﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Globalization;
using System.IO;
using Microsoft.Synchronization.Data;
using System.Data.SqlClient;
using SyncLib.Interface;

namespace SyncLib.Data
{
    public delegate void SerializeBatch(KDataBatch batch);
    public class KDataBatchProducer
    {

        SqlConnection _Connection;
        SqlTransaction _Transaction;
        Guid _ClientId;
        string _Location;
        public string BatchDirectory { get; set; }
        public string PrefixFileName { get; set; }
        public int CurrentBatchIndex { get; set; }
        public int BatchCount { get; set; }
        public int BatchSize { get; set; }

        public DataSet Data { get; set; }
        public SerializeBatch DelegateSerializeBatch;

        long CurrentBatchSize;

        public KDataBatchProducer(string name, SqlConnection conn, SqlTransaction tran, Guid clientId, string location)
        {
            _Connection = conn; _Transaction = tran; _ClientId = clientId; _Location = location;
            Data = new DataSet(name);

            CurrentBatchIndex = 0;
            BatchCount = 0;
            BatchSize = 0;
            CurrentBatchSize = 0;
        }

        public void AddReader(IDataReader reader, string tableName)
        {
            DataTable tblDataSchema = BuildDataTable(tableName, reader);
            long maxBatchSize = BatchSize * 1024L;
            if (BatchSize <= 0)
                maxBatchSize = long.MaxValue;

            if (Data.Tables.Contains(tableName))
                Data.Merge(tblDataSchema, false, MissingSchemaAction.Add);
            else
                Data.Tables.Add(tblDataSchema.Clone());



            int rowIndex = 0;
            DataTable tblData = Data.Tables[tblDataSchema.TableName];
            while (reader.Read())
            {

                DataRow newRow = tblData.NewRow();
                PopulateFullRow(reader, newRow);
                long currentRowSize = GetRowSizeFromDataRow(newRow);
                if (currentRowSize > maxBatchSize)
                {
                    throw new Exception("One row of table " + tableName + " has size " + currentRowSize + " it greater than maxsize " + maxBatchSize);
                }

                if ((currentRowSize + CurrentBatchSize) < maxBatchSize)
                {
                    tblData.Rows.Add(newRow);
                    CurrentBatchSize += currentRowSize;
                }
                else
                {

                    SerializeBatch();
                    DataSet dsOld = Data;
                    Data = dsOld.Clone();
                    tblData.Rows.Add(newRow.ItemArray);
                    dsOld.Clone();
                    CurrentBatchSize = currentRowSize;
                    CurrentBatchIndex++;
                    BatchCount++;
                    tblData = Data.Tables[tblDataSchema.TableName];

                }
                rowIndex++;
            }
        }
        public void EndBatchProducer()
        {
            if (CurrentBatchSize > 0)
            {

                SerializeBatch();
                Data.Clear();
                Data.AcceptChanges();
                CurrentBatchIndex++;
                BatchCount++;
            }
            _Connection = null;
            _Transaction = null;
        }

        private void SerializeBatch()
        {
            KDataBatch batch = new KDataBatch();
            batch.BatchIndex = CurrentBatchIndex;
            batch.DataSetSurrogate = new DataSetSurrogate(Data);
            batch.FileName = PrefixFileName + "_" + batch.BatchIndex.ToString() + ".depen";
            batch.BatchSize = CurrentBatchSize;

            if (DelegateSerializeBatch != null)
                DelegateSerializeBatch(batch);
            else
            {
                if (!Directory.Exists(BatchDirectory))
                {
                    Directory.CreateDirectory(BatchDirectory);
                }
                KDataBatchFactory.Serialize(batch, BatchDirectory + "\\" + batch.FileName);
            }
        }

        public void SaveBatchToSQL(KDataBatch batch)
        {
            using (SqlConnection conn = new SqlConnection(CommonVariable.SqlConnectionString))
            {
                using (SqlCommand cmd = new SqlCommand("InsertSyncDataBatch", conn))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.Add("@ClientId", SqlDbType.UniqueIdentifier).Value = _ClientId;
                    cmd.Parameters.Add("@Location", SqlDbType.VarChar, 32).Value = _Location;
                    cmd.Parameters.Add("@BatchKey", SqlDbType.NVarChar, 512).Value = batch.FileName;
                    byte[] contents = KDataBatchFactory.GetBytes(batch);
                    cmd.Parameters.Add("@BatchContent", SqlDbType.Image).Value = contents;
                    cmd.Parameters.Add("@ContentLength", SqlDbType.Int).Value = contents.Length;
                    cmd.ExecuteNonQuery();
                }
            }
        }
        void PopulateFullRow(IDataReader reader, DataRow row)
        {
            row.BeginEdit();
            for (int i = 0; i < reader.FieldCount; i++)
            {

                row[i] = reader.GetValue(i);

            }
            row.EndEdit();
        }
        private DataTable BuildDataTable(string name, IDataReader enumQueryResults)
        {
            DataTable table = new DataTable(name);
            table.Locale = CultureInfo.InvariantCulture;
            for (int i = 0; i < enumQueryResults.FieldCount; i++)
            {
                table.Columns.Add(enumQueryResults.GetName(i), enumQueryResults.GetFieldType(i));
            }
            return table;
        }
        internal static long GetRowSizeFromDataRow(DataRow row)
        {
            bool flag = false;
            if (row.RowState == DataRowState.Deleted)
            {
                row.RejectChanges();
                flag = true;
            }
            long num = 0L;
            foreach (object obj2 in row.ItemArray)
            {
                string s = obj2 as string;
                byte[] buffer = obj2 as byte[];
                if (obj2 is DBNull)
                {
                    num += 5L;
                }
                else if (obj2 is Guid)
                {
                    num += 0x10L;
                }
                else if (s != null)
                {
                    num += Encoding.Unicode.GetByteCount(s);
                }
                else if (buffer != null)
                {
                    num += buffer.Length;
                }
                else
                {
                    num += GetSizeForType(obj2.GetType());
                }
            }
            if (flag)
            {
                row.Delete();
            }
            return num;
        }
        internal static long GetRowSizeFromReader(IDataReader reader)
        {
            long num = 0L;
            for (int i = 0; i < reader.FieldCount; i++)
            {

                Type fieldType = reader.GetFieldType(i);
                if (reader.IsDBNull(i))
                {
                    num += 5L;
                }
                else if (fieldType == typeof(Guid))
                {
                    num += 0x10L;
                }
                else if (fieldType == typeof(byte[]))
                {
                    num += reader.GetBytes(i, 0L, null, 0, 0);
                }
                else if (fieldType == typeof(string))
                {
                    num += reader.GetChars(i, 0L, null, 0, 0) * 2L;
                }
                else
                {
                    num += GetSizeForType(fieldType);
                }
            }
            return num;
        }
        internal static long GetSizeForType(Type type)
        {
            switch (Type.GetTypeCode(type))
            {
                case TypeCode.Empty:
                    return 0L;

                case TypeCode.Object:
                case TypeCode.Int64:
                case TypeCode.UInt64:
                case TypeCode.Double:
                case TypeCode.DateTime:
                    return 8L;

                case TypeCode.Boolean:
                case TypeCode.SByte:
                case TypeCode.Byte:
                    return 1L;

                case TypeCode.Char:
                case TypeCode.Int16:
                case TypeCode.UInt16:
                    return 2L;

                case TypeCode.Int32:
                case TypeCode.UInt32:
                case TypeCode.Single:
                    return 4L;

                case TypeCode.Decimal:
                    return 0x10L;
            }
            return 0L;
        }
    }
}
